/*
 author: FENG Xu, NUNES Thomas
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
 ====================
 Macros
 ====================
 */
#define PORT_DEFAULT        "5000"
#define ADR_DEFAULT 	    "127.0.0.1"

#define LENGTH_OF_LISTEN_QUEUE     20
#define BUFFER_SIZE                1024
#define FILE_NAME_MAX_SIZE         512
#define MAX_CMD_SIZE               64
/*
 ====================
 Types
 ====================
 */
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;
typedef char* STRING;

typedef enum {
    LS = 0, PUT, GET
} COMMAND;

/*
 ====================
 Fonctions
 ====================
 */
void helpProgramme(STRING nomPgm) {
    printf("\nUtilisation du programme :\n");
    printf("%s <nom ou adresse IP du serveur> <numero de port du serveur>\n", nomPgm);
    printf("Attention: le numero de port doit etre un entier entre 1000 et 99999 inclus.\n\n");
}

int main(int argc, char* argv[]) {
    int continueRunning = 1;
    STRING nomProgramme = argv[0];
    STRING stringServeur;
    STRING stringPort;
    int numPort;
    char* endptr = (char*) 1;
    char buffer[BUFFER_SIZE];
    char nomfichier[FILE_NAME_MAX_SIZE + 1];
    char command[MAX_CMD_SIZE + 1];
    COMMAND cmd;
    int length;
    int file_block_length;
    int erreur;
           
     printf("\n============================\n"
             "========= SERVEUR ==========\n"
             "============================\n");

	if ((argc > 1 && !strcmp(argv[1], "help"))) {// menu d'aide
	    helpProgramme(nomProgramme);
	}

	if (argc > 3) {// trop d'argument
	    if ((argc >= 1 && !strcmp(argv[0], "help"))) {
		printf("\n/!\\Attention erreur/!\\: nombre de parametres incorect.\n");
	    }
	    helpProgramme(nomProgramme);
	    return 1;
	} else {
	    if (argc != 3) { // valeurs par défault
		stringServeur = ADR_DEFAULT;
		stringPort = PORT_DEFAULT;
	    } else {// Si les valeurs sont définies dans les arguments
		stringServeur = argv[1];
		stringPort = argv[2];
	    }
	}	

    /* string du port => numero du port */
    numPort = strtol(stringPort, &endptr, 0);

    if ((*endptr != '\0') || (numPort < 1000) || (numPort >= 100000)) {
        /* erreur si le numero de port n'est pas entier
         ou s'il est un port de bas niveau, ou s'il est un port de tres haut niveau */
        printf("\n/!\\Attention erreur/!\\: numero de port invalide.\n");
        helpProgramme(nomProgramme);
        return 1;
    }
     printf("\n Serveur lancé.\n"
	    "\n Port: %s"
            "\n adresse:%s\n attente de client ...\n",stringPort, stringServeur);

    SOCKADDR_IN server_addr;
    bzero(&server_addr, sizeof (server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htons(INADDR_ANY);
    server_addr.sin_port = htons(numPort);

    SOCKET server_socket = socket(PF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        printf("\n/!\\Attention erreur/!\\: erreur dans la creation de socket.\n");
        return 1;
    }

    if (bind(server_socket, (SOCKADDR*) & server_addr, sizeof (server_addr))) {
        printf("\n/!\\Attention erreur/!\\: bind port du serveur.\n");
        return 1;
    }

    // server_socket sert a ecouter
    if (listen(server_socket, LENGTH_OF_LISTEN_QUEUE)) {
        printf("\n/!\\Attention erreur/!\\: ecoute impossible du serveur.\n");
        return 1;
    }

    SOCKADDR_IN client_addr;
    socklen_t client_addr_length = sizeof (client_addr);

    while (continueRunning) {

        SOCKET new_server_socket = accept(server_socket, (SOCKADDR*) & client_addr, &client_addr_length);
        if (new_server_socket < 0) {
            printf("\n/!\\Attention erreur/!\\: accept du serveur.\n");
            continue;
        }

        //recevoir la commande du client
        bzero(buffer, sizeof (buffer));
        length = recv(new_server_socket, buffer, BUFFER_SIZE, 0);
        if (length < 0) {
            printf("\n/!\\Attention erreur/!\\: reception de la commande.\n");
            continue;
        }

        bzero(command, sizeof (command));
        strncpy(command, buffer, strlen(buffer) > MAX_CMD_SIZE ? MAX_CMD_SIZE : strlen(buffer));

        //reconnaitre la commande
        if (!strcmp(command, "ls")) {
            cmd = LS;
        } else if (!strcmp(command, "get")) {
            cmd = GET;
        } else if (!strcmp(command, "put")) {
            cmd = PUT;
        }

        switch (cmd) {
            case LS:
                //executer ls chez le serveur et ecrire le resultat dans un fichier temporel .ls_result
                system("ls -CF > .ls_result");

                //ouvrir le fichier ls_result.tmp
                FILE* fp_ls = fopen(".ls_result", "r");
                if (fp_ls == NULL) break;

                //envoyer le resultat au client
                bzero(buffer, sizeof (buffer));
                file_block_length = 0;
                erreur = 0;
                while ((file_block_length = fread(buffer, sizeof (char), BUFFER_SIZE, fp_ls)) > 0) {
                    if (send(new_server_socket, buffer, file_block_length, 0) < 0) {
                        erreur = 1;
                        break;
                    }
                    bzero(buffer, sizeof (buffer));
                }
                fclose(fp_ls);

                //supprimer le fichier temporel .ls_result
                system("rm -f .ls_result");

                if (!erreur) printf("\nLe resultat de \"ls\" a ete envoye.\n");
                break;

            case GET:
                //recevoir le nom du fichier demander
                bzero(buffer, sizeof (buffer));
                length = recv(new_server_socket, buffer, BUFFER_SIZE, 0);
                if (length < 0) {
                    printf("\n/!\\Attention erreur/!\\: mauvaise reception du nom de fichier.\n");
                    break;
                }

                bzero(nomfichier, sizeof (nomfichier));
                strncpy(nomfichier, buffer, strlen(buffer) > FILE_NAME_MAX_SIZE ? FILE_NAME_MAX_SIZE : strlen(buffer));

                //ouvrir le fichier local
                FILE* fp_get = fopen(nomfichier, "r");
                if (fp_get == NULL) {
                    //envoyer le message erreur au client
                    bzero(buffer, sizeof (buffer));
                    strcpy(buffer, "ERR_FICHIER_N_EXISTE_PAS");
                    while (send(new_server_socket, buffer, BUFFER_SIZE, 0) < 0) {
                    }//arreter jusqu'a la reussite de l'envoie
                    break;
                }

                //envoyer le fichier au client
                bzero(buffer, sizeof (buffer));
                file_block_length = 0;
                erreur = 0;
                while ((file_block_length = fread(buffer, sizeof (char), BUFFER_SIZE, fp_get)) > 0) {
                    if (send(new_server_socket, buffer, file_block_length, 0) < 0) {
                        printf("\n/!\\Attention erreur/!\\:Le fichier  %s n'a pas pu être envoyé.\n", nomfichier);
                        erreur = 1;
                        break;
                    }

                    bzero(buffer, sizeof (buffer));
                }
                fclose(fp_get);
                if (!erreur) printf("\nLe Fichier %s a ete transferé.\n", nomfichier);
                break;

            case PUT:
                //recevoir le nom du fichier
                bzero(buffer, sizeof (buffer));
                length = recv(new_server_socket, buffer, BUFFER_SIZE, 0);
                if (length < 0) {
                    printf("\n/!\\Attention erreur/!\\: reception du nom de fichier incorect.\n");
                    break;
                }

                bzero(nomfichier, sizeof (nomfichier));
                strncpy(nomfichier, buffer, strlen(buffer) > FILE_NAME_MAX_SIZE ? FILE_NAME_MAX_SIZE : strlen(buffer));

                //ouvrir le fichier local
                FILE* fp_put = fopen(nomfichier, "w");
                if (fp_put == NULL) {
                    printf("\n/!\\Attention erreur/!\\:Le fichier %s ne peut pas etre ouvert et ecrit dedans.\n", nomfichier);
                    break;
                }

                // recevoir le fichier du client et ecrire dans le fichier local
                bzero(buffer, sizeof (buffer));
                length = 0;
                erreur = 0;
                while ((length = recv(new_server_socket, buffer, BUFFER_SIZE, 0))) {
                    if (length < 0) {
                        printf("\n/!\\Attention erreur/!\\: reception des donnees du serveur.\n");
                        erreur = 1;
                        break;
                    }
                    int write_length = fwrite(buffer, sizeof (char), length, fp_put);
                    if (write_length < length) {
                        printf("\n/!\\Attention erreur/!\\: ecriture du fichier %s impossible.\n", nomfichier);
                        erreur = 1;
                        break;
                    }
                    bzero(buffer, BUFFER_SIZE);
                }
                fclose(fp_put);
                if (!erreur) printf("\n Le Fichier %s a été transmit au client du client.\n", nomfichier);
                else system(strcat("rm -f ", nomfichier)); //si erreur, supprimer le fichier creer
                break;
            default:;
        }
        close(new_server_socket);
    }
    close(server_socket);
    return 0;
}
